package org.zebra.mybatis.plus.ext.redundancy;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.dromara.streamquery.stream.plugin.mybatisplus.Database;
import org.springframework.util.StringUtils;
import org.zebra.mybatis.plus.core.MapperPlus;
import org.zebra.mybatis.plus.ext.redundancy.annotation.RedundancyField;
import org.zebra.mybatis.plus.ext.redundancy.description.RedundancyFieldConditionDescription;
import org.zebra.mybatis.plus.ext.redundancy.description.WaitUpdateDescription;
import org.zebra.mybatis.plus.ext.redundancy.description.WaitUpdateFieldDescription;
import org.zebra.mybatis.plus.ext.redundancy.event.EntityUpdateEvent;
import org.zebra.mybatis.plus.ext.redundancy.signature.ConditionSignature;
import org.zebra.mybatis.plus.ext.redundancy.signature.DescriptionSignature;
import org.zebra.mybatis.plus.ext.redundancy.util.BeanClassUtil;
import org.zebra.mybatis.plus.util.MyBatisPlusUtil;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhanghongbin
 */
@Slf4j
public class RedundancyFieldManager {

    /**
     * 实体中，待更新字段分组缓存
     */
    public static final Map<String, Map<DescriptionSignature, WaitUpdateDescription>> ENTITY_GROUP_CACHE_MAP =
            new HashMap<>();

    /**
     * 实体下，字段与方法的缓存
     */
    private static final Map<String, Map<String, Method>> ENTITY_FIELD_METHOD_CACHE_MAP = new HashMap<>();

    /**
     * 添加 某个类的某个字段，需要被数据冗余
     *
     * @param entityClass     某个类
     * @param entityField     某个字段
     * @param redundancyField 冗余条件
     * @return true,false
     */
    public static boolean addRedundancyField(Class<?> entityClass, Field entityField, RedundancyField redundancyField) {

        Class<?> sourceClass = redundancyField.source();
        if (sourceClass == Void.class) {
            log.error("{}类上的{}字段，@RedundancyField缺少`source`属性，" + "自动更新数据功能将被忽略", entityClass, entityField.getName());
            return false;
        }
        String sourceName = sourceClass.getName();
        DescriptionSignature descriptionSignature = new DescriptionSignature(
                entityClass,
                sourceClass,
                Arrays.stream(redundancyField.conditions())
                        .map(con -> new ConditionSignature(con.selfField(), con.sourceField()))
                        .collect(Collectors.toList()),
                redundancyField.updateCondition());
        // 根据源类全名称，找到其关联的所有需要更新的记录，并根据更新条件分组，进行分组更新
        WaitUpdateDescription waitUpdateDescription = ENTITY_GROUP_CACHE_MAP
                .computeIfAbsent(sourceName, k -> new HashMap<>(4))
                .computeIfAbsent(descriptionSignature, k -> {
                    List<RedundancyFieldConditionDescription> conditionDescriptions = Arrays.stream(
                                    redundancyField.conditions())
                            .map(con -> {
                                Field selfField = BeanClassUtil.getField(entityClass, con.selfField());
                                Field sourceField = BeanClassUtil.getField(sourceClass, con.sourceField());
                                return new RedundancyFieldConditionDescription(selfField, sourceField);
                            })
                            .collect(Collectors.toList());
                    return new WaitUpdateDescription(
                            entityClass,
                            sourceClass,
                            redundancyField.updateCondition(),
                            conditionDescriptions,
                            new ArrayList<>());
                });

        Field sourceField = BeanClassUtil.getField(sourceClass, redundancyField.field());
        waitUpdateDescription.getWaitUpdateFields().add(new WaitUpdateFieldDescription(entityField, sourceField));
        return true;
    }

    /**
     * 更新
     *
     * @param entityUpdateEvent     源数据事件
     * @param waitUpdateDescription 待更新数据的描述
     * @param <E>                   待更新的实体
     */
    public static <E> void executeUpdate(
            EntityUpdateEvent<?> entityUpdateEvent, WaitUpdateDescription waitUpdateDescription) {

        Object sourceEntity = entityUpdateEvent.getEntity();

        // 组装update slq语句的set部分
        UpdateWrapper<E> updateWrapper = null;
        for (WaitUpdateFieldDescription waitUpdateField : waitUpdateDescription.getWaitUpdateFields()) {
            String sourceFieldName = waitUpdateField.getSourceFieldName();
            if (entityUpdateEvent.getFields().isEmpty()
                    || entityUpdateEvent.getFields().contains(sourceFieldName)) {

                if (updateWrapper == null) {
                    updateWrapper = Wrappers.update();
                }

                try {
                    Object sourceFieldVal = getFieldVal(sourceEntity, sourceFieldName);
                    // 组装sql
                    updateWrapper.set(waitUpdateField.getEntityColumnName(), sourceFieldVal);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        // 当前bean存在需要被set的字段，组装update sql的where条件部分
        if (updateWrapper != null) {
            for (RedundancyFieldConditionDescription condition : waitUpdateDescription.getDataSourceConditions()) {
                try {
                    String sourceConditionFieldName = condition.getSourceFieldName();
                    // 获取源字段的读取方法，并缓存
                    Object sourceConditionFieldVal = getFieldVal(sourceEntity, sourceConditionFieldName);
                    updateWrapper.eq(condition.getSelfColumnName(), sourceConditionFieldVal);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 拼接自定义的sql条件
            String updateCondition = waitUpdateDescription.getUpdateCondition();
            if (StringUtils.hasText(updateCondition)) {
                updateWrapper.apply(updateCondition);
            }
            // 获取mapper， 执行sql
            Class<E> waitUpdateEntityClass = (Class<E>) waitUpdateDescription.getEntityClass();
            Class<?> mapperClass = Database.getMapperClass(waitUpdateEntityClass);
            MapperPlus<E> mapperPlus = MyBatisPlusUtil.getMapperPlus(mapperClass);
            mapperPlus.update(null, updateWrapper);
        }
    }

    private static Object getFieldVal(Object sourceEntity, String sourceFieldName)
            throws IllegalAccessException, InvocationTargetException {
        Class<?> sourceEntityClass = sourceEntity.getClass();
        String sourceEntityClassName = sourceEntityClass.getName();
        // 获取并构建该源相关的缓存
        Map<String, Method> sourceEntityFieldMethodCache =
                ENTITY_FIELD_METHOD_CACHE_MAP.computeIfAbsent(sourceEntityClassName, k -> new HashMap<>(1));
        // 获取源字段的读取方法，并缓存
        Method sourceFieldReadMethod = sourceEntityFieldMethodCache.computeIfAbsent(
                sourceFieldName, k -> BeanClassUtil.getReadMethod(sourceEntityClass, sourceFieldName));
        // 获取源字段值
        return sourceFieldReadMethod.invoke(sourceEntity);
    }
}
